/**
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain
 * a copy of the License at
 * 
 *         http://www.apache.org/licenses/LICENSE-2.0
 *         
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. Unless required by applicable law or
 * agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 */
package org.rhok.pdx.ncn.communicator;

import java.io.DataInputStream;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.json.JSONArray;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.rhok.pdx.ncn.datatypes.LocationData;

import android.util.Log;

/**
 * Web client for connecting to the remove server.
 * @author pfritzsche
 */
public class WebClient {
	private static final String
		SERVER_URL = "http://cellstrength.herokuapp.com/",
		TAG = "WebClient";
	
	private static final double RANGE = 0.1;
	private static final int
		MAX_BACKLOG_LEN = 1000,
		MAX_COUNT = 100,
		TIMEOUT = 30000; // 30 seconds in milliseconds
	
	private JSONArray backlog = new JSONArray();
	
	/**
	 * Retrieves all data points from the server near the current location, starting at the time
	 * specified. TODO(pfritzsc): refactor to make method shorter.
	 * @param data the most recent location from the phone
	 * @param startTime the earliest time to ask for music
	 * @return LinkedList<LocationData> containing all responses from server
	 * @throws Exception thrown when there are server I/O issues or JSON formation issues
	 */
	public List<LocationData> getServerData(LocationData data) throws Exception {
		long startTime = (data != null) ? data.timestamp() : System.currentTimeMillis() - 86400000;
		
		DefaultHttpClient client = new DefaultHttpClient();
		HttpParams params = client.getParams();
		HttpConnectionParams.setConnectionTimeout(params, TIMEOUT);
		HttpConnectionParams.setSoTimeout(params, TIMEOUT);
		ConnManagerParams.setTimeout(params, TIMEOUT);
		
		StringBuilder sb = new StringBuilder();
		sb.append(SERVER_URL).append("?lat=").append(data.lat());
		sb.append("&lng=").append(data.lng());
		sb.append("&range=").append(RANGE);
		sb.append("&max_count=").append(MAX_COUNT);
		sb.append("&timestamp=").append(startTime);
		
		HttpGet request = new HttpGet();
		request.addHeader("Content-Type", "application/x-www-form-urlencoded");
		request.setURI(new URI(sb.toString()));
		
		HttpResponse httpResponse = client.execute(request);
		int status = httpResponse.getStatusLine().getStatusCode();

		List<LocationData> results = new LinkedList<LocationData>();
		if (status == HttpStatus.SC_OK) {
			DataInputStream dis = new DataInputStream(httpResponse.getEntity().getContent());
			JSONObject jsonResult = (JSONObject) new JSONTokener(dis.readLine()).nextValue();
			if (jsonResult.has("measurements")) {
				JSONArray objects = jsonResult.getJSONArray("measurements");
				for (int i = 0; i < objects.length(); i++) {
					JSONObject o = objects.getJSONObject(i);
					JSONObject loc = o.getJSONObject("location");
					results.add(new LocationData(
							o.getLong("timestamp"), loc.getDouble("lat"), loc.getDouble("lng"),
							o.getInt("signal"), o.getInt("wifi")));
				}
			}
		}
		return results;
	}
	
	/**
	 * Submits data to the server for storing remotely. Maintains a backlog of items unable to be
	 * sent due to lack of connection. At most [MAX_BACKLOG_LEN] items are kept at any given point.
	 * If the limit is reached, 10% of the current queue is removed to make room for new data
	 * points. The oldest 10% is dropped.
	 * @param data queue of data to send to the server
	 * @return true if the submission succeeded
	 * @throws Exception on I/O or JSON errors
	 */
	public boolean submitData(ConcurrentLinkedQueue<LocationData> data) throws Exception {
		while (!data.isEmpty()) {
			backlog.put(data.poll().toJSON());
		}
		JSONObject json = new JSONObject();
		json.put("measurements", backlog);
		
		DefaultHttpClient client = new DefaultHttpClient();
		HttpPost request = new HttpPost(SERVER_URL);
		request.setHeader("Accept", "application/json");
		request.setHeader("Content-type", "application/json");
		
		StringEntity se = new StringEntity(json.toString());
		request.setEntity(se);

		Log.d(TAG, "Submitting request to server: " + json.toString());
		HttpResponse httpResponse = client.execute(request);
		int status = httpResponse.getStatusLine().getStatusCode();
		Log.d(TAG, "Server response code: " + status);
		
		if (status == HttpStatus.SC_OK) {
			backlog = new JSONArray();
			return true;
		}
		
		if (backlog.length() > MAX_BACKLOG_LEN) {
			JSONArray temp = new JSONArray();
			for (int i = 0; i < (MAX_BACKLOG_LEN * 0.9); i++) {
				temp.put(backlog.get(i));
			}
			backlog = temp;
		}
		return false;
	}
}